#ifndef TRAFFIC_SIM_H
#define TRAFFIC_SIM_H

#include <iostream>
#include <cstdlib>
#include <ctime>
#include <sstream>
#include <gsl/gsl_rng.h>
#include <gsl/gsl_randist.h>
#include <tpl_sim_agent_graph.H>
#include <Set.H>

using namespace Aleph;
using namespace std;

namespace Traffic_Sim 
{
  #define DELAY_TIME_IN_NODES 2

  class Traffic_Agent_Impl;
  struct Traffic_Node_Info;
  struct Traffic_Arc_Info;

  typedef Sim_Agent_Node<Traffic_Node_Info> Traffic_Node;
  typedef Sim_Agent_Arc<Traffic_Arc_Info> Traffic_Arc;
  typedef Sim_Agent<Traffic_Agent_Impl*> Traffic_Agent;
  typedef Sim_Agent_Graph<Traffic_Node, Traffic_Arc, Traffic_Agent> Traffic_Graph;

  class Traffic_Semaphore;
  class Traffic_Node_Arrival_Event;
  class Traffic_Node_Departure_Event;
  class Traffic_Agent_Destruction_Event;
  
	
	struct Traffic_Node_Info
  {
    string description;
    //Referencia a agente tricolor
    Traffic_Semaphore * semaphore;

    float x;
    float y;

    Traffic_Node_Info (const string & desc, float cx, float cy ) 
      : description(desc), x(cx), y(cy) 
    { 
      semaphore = NULL; 
    }
   
    Traffic_Node_Info (const string& desc) : description(desc)
    { 
      semaphore = NULL; 
    }	
    
    bool operator == (const Traffic_Node_Info & n) const
    {
      return strcasecmp(n.description.c_str(), description.c_str()) == 0;
    }
  };

  struct Traffic_Arc_Info
  {
    string description;

    long delay_time;

    int vehicle_capacity;

    Traffic_Arc_Info () { }
    Traffic_Arc_Info (const string & desc, long delay, int capacity) 
      : description(desc), delay_time(delay), vehicle_capacity(capacity) { /* Empty */ }

    long get_delay_time()
    {
      return delay_time * ( 1 + ( 20 / vehicle_capacity ));
    }

  };
  struct Cmp_Traffic_Arc
  {
    const bool operator () (string op1, string op2)
    {
      return op1.compare(op2) > 0;
    }
  };

  typedef set<string, Cmp_Traffic_Arc> Arc_Set;

  enum Time_Distribution 
  {
    Constant,
    Exponential,
    Uniform,
    Normal
  };

  enum Source_Mode 
  {
    Reactive_Mode,
    Sleep_Mode,
    Count_Mode,
    Probabilistic_Mode
  };

  class Random_Generator
  {
   private:
    gsl_rng * r;

    void initialize_rand_gen();
		Random_Generator()
		{
 			initialize_rand_gen();
		}

		~Random_Generator()
		{
			gsl_rng_free (r);
		}
    Random_Generator(Random_Generator const&);
    Random_Generator& operator=(Random_Generator const&);

   public:
		static Random_Generator & get_instance()
		{
  		static Random_Generator instance;
  		return instance;
		}
    double get_uniform_rnd(double lower, double upper);
    unsigned long get_random_int (long limit);
    double get_exponential_rnd(double mean);
    double get_normal_rnd (double mean, double std_dev);
    unsigned int get_poisson_rnd (double mean);
  };

  class Random_Time
  {
   private:
    // Time calculation
    Time_Distribution time_distribution;
    // For constant, constant time
    // For exponential and normal distributions, mean
    // For flat distribution, lower bound
    double time_dist_param_1;
    // For normal distribution, standard deviation
    // For flat distribution, upper bound
    double time_dist_param_2;
 
    void init(Time_Distribution dist, double param1, double param2);

   public:
    Random_Time(); 
    Random_Time(Time_Distribution dist, double param1);
    Random_Time(Time_Distribution dist, double param1, double param2);
    long get_time();
	};
	

  class Traffic_Agent_Impl
  {
   public:
    typedef void (*Update_Callback)(void*);

   protected:
    int id;
    string description;
    Update_Callback update_callback;
    void * callback_cookie;
    virtual void execute(Traffic_Agent * agent, Traffic_Graph * graph);
    void update_graphics();
    Traffic_Agent * agent;

   public:
    Traffic_Agent_Impl(int id, const string & desc);
    virtual ~Traffic_Agent_Impl();
    bool operator == (const Traffic_Agent_Impl & otroAgente) const;
    int get_id();
    string get_description();
    const Traffic_Agent & get_agent() const;
    void set_update_callback(Update_Callback callback, void * cookie);
    static void traffic_agent_execution(void * graph_ptr, void * agent_ptr);
    static void create_agent_in_node(Traffic_Agent_Impl * agent, 
      Traffic_Graph * graph, Traffic_Node * node, long activation_time);
    static void create_agent_in_arc(Traffic_Agent_Impl * agent, 
      Traffic_Graph * graph, Traffic_Arc * arc, long activation_time);
  };

  class Traffic_Node_Arrival_Event : public Sim_Event
  {
   public:
    Traffic_Agent * agent;
    Traffic_Node * to_node;
    Traffic_Arc * from_arc;
    Traffic_Node_Arrival_Event(Traffic_Agent * _agent, Traffic_Node * _to_node, 
      Traffic_Arc * _from_arc, long time);
    virtual ~Traffic_Node_Arrival_Event();
    virtual void execute_event(void * graph_ptr);
  };

  class Traffic_Node_Departure_Event : public Sim_Event
  {
   public:
    Traffic_Agent * agent;
    Traffic_Node * from_node;
    Traffic_Arc * to_arc;
    Traffic_Node_Departure_Event(Traffic_Agent * _agent, Traffic_Node * _from_node, 
      Traffic_Arc * _to_arc, long time);
    ~Traffic_Node_Departure_Event();
    virtual void execute_event(void * graph_ptr);    
  };

  class Traffic_Agent_Destruction_Event : public Sim_Event
  {
   public:
    Traffic_Agent * agent;
    Traffic_Agent_Destruction_Event(Traffic_Agent * _agent, long time);
    virtual ~Traffic_Agent_Destruction_Event();
    virtual void execute_event(void * graph_ptr);
  };

  class Traffic_Semaphore_Check_Event : public Sim_Event
  {
   public:
    Traffic_Agent * agent;
    Traffic_Node * in_node;
    Traffic_Arc * from_arc;
    Traffic_Arc * to_arc;
    Traffic_Semaphore_Check_Event(Traffic_Agent * _agent, Traffic_Node * _in_node, 
        Traffic_Arc * _from_arc, Traffic_Arc * _to_arc, long time);
    virtual ~Traffic_Semaphore_Check_Event();
    virtual void execute_event(void * graph_ptr);
  };

	enum Vehicle_Event_Type
	{
		Agent_Creation,
		Agent_Destruction,
		Node_Arrival,
		Node_Departure,
    Semaphore_Check
	};

  class Traffic_Vehicle : public Traffic_Agent_Impl
  {
   protected:
    bool waiting_destruction;
    bool waiting_for_semaphore;
    Vehicle_Event_Type last_event;
    virtual void execute(Traffic_Agent * agent, Traffic_Graph * graph);
    virtual Traffic_Node * choose_initial_direction(Traffic_Graph * graph, 
      Traffic_Arc * initial_arc);
    virtual Traffic_Arc * choose_next_arc(Traffic_Graph * graph, 
      Traffic_Node * current_node, Arc_Set * exclusion_set);

   public:
    Traffic_Vehicle(int id, const string & desc);
    virtual ~Traffic_Vehicle();
    Traffic_Node* previous_node;
    Vehicle_Event_Type get_last_event();
    bool is_waiting_destruction();
    bool is_waiting_for_semaphore();
    virtual void notify_node_arrival(Traffic_Graph * graph, Traffic_Agent * agent, 
      Traffic_Node * to_node, Traffic_Arc * from_arc);
    virtual void notify_node_departure(Traffic_Graph * graph, Traffic_Agent * agent, 
      Traffic_Node * from_node, Traffic_Arc * to_arc);
    virtual void notify_agent_destruction();
    virtual void notify_semaphore_check(Traffic_Graph * graph, Traffic_Agent * agent, 
      Traffic_Node * in_node, Traffic_Arc * from_arc, Traffic_Arc * to_arc);
  };

  class Traffic_Random_Vehicle : public Traffic_Vehicle
  {
   protected:
    virtual Traffic_Node * choose_initial_direction(Traffic_Graph * graph, 
      Traffic_Arc * initial_arc);
    virtual Traffic_Arc * choose_next_arc(Traffic_Graph * graph, 
      Traffic_Node * current_node, Arc_Set * exclusion_set);

   public:
    Traffic_Random_Vehicle(int id, const string & desc);
    virtual ~Traffic_Random_Vehicle();
  };

  class Traffic_Searching_Vehicle : public Traffic_Random_Vehicle
  {
    Traffic_Node * destination_node;
   public:
    Traffic_Searching_Vehicle(int id, const string & desc, Traffic_Node * destination);
    virtual ~Traffic_Searching_Vehicle();
    virtual void notify_node_arrival(Traffic_Graph * graph, Traffic_Agent * agent, 
      Traffic_Node * to_node, Traffic_Arc * from_arc);
  };

  enum Semaphore_Color
  {
    Red,
    Yellow,
    Green
  };

  class Traffic_Semaphore : public Traffic_Agent_Impl
  {
   protected:
    Traffic_Node * parent_node;
    Traffic_Arc** arc_index_table;
    int arc_count;
    int current_state;
    int states_count;
    virtual Semaphore_Color get_color_state(int arc_from, int arc_to);
    virtual long get_next_change_delay();
    virtual void execute(Traffic_Agent * agent, Traffic_Graph * graph);
    
   public:
    Traffic_Semaphore (int id, const string & desc, Traffic_Node * parent, 
      int _arc_count);
    virtual ~Traffic_Semaphore();
    void set_arc_index(Traffic_Arc * arc, int index);
    int find_arc_index(Traffic_Arc * arc);
    Traffic_Arc * get_arc(int index);
    int get_arc_count();
    Semaphore_Color get_color (Traffic_Arc * arc_from, Traffic_Arc * arc_to);
    virtual Semaphore_Color get_color_to_show(int arc_index);
  };


  class Three_Way_Traffic_Semaphore : public Traffic_Semaphore
  {
   protected:
    virtual Semaphore_Color get_color_state(int arc_from, int arc_to);
    virtual long get_next_change_delay();
    
   public:
    Three_Way_Traffic_Semaphore(int id, const string & desc, 
      Traffic_Node * parent, int _arc_count);
    virtual ~Three_Way_Traffic_Semaphore();
    virtual Semaphore_Color get_color_to_show(int arc_index);
  };
 
  class Traffic_Agent_Creation_Event : public Sim_Event
  {
   public:
    typedef void (*Create_Renderer)(Traffic_Agent_Impl*);
    typedef Traffic_Agent_Impl * (*Create_Agent_Impl)(long id);
   private:
    long next_agent_id;

    // Occurrence Poisson distribution config
    double occurrence_mean;

    Source_Mode source_mode;
    time_t start_time;
    long sleep_time;
    long seconds_to_stop;
    long iterations_to_stop;
    double stop_probability;
    long it_count;
    bool keep_running();
    Create_Renderer create_renderer_callback;
    Create_Agent_Impl create_agent_impl_callback;
    Random_Time * agent_time;
    Random_Time delay_time;
    
   public:
    Traffic_Agent_Creation_Event(Create_Agent_Impl callback, Random_Time * _agent_time, long _time);
    virtual ~Traffic_Agent_Creation_Event();
    Source_Mode get_mode();
    void set_mode(Source_Mode value);
    long get_sleep_time();
    void set_sleep_time(long value);
    long get_seconds_to_stop();
    void set_seconds_to_stop(long value);
    long get_iterations_to_stop();
    void set_iterations_to_stop(long value);
    double get_stop_probability();
    void set_stop_probability(double value);
    double get_occurrence_mean();
    void set_occurrence_mean(double value);
    void set_next_agent_id(long id);
    long get_next_agent_id();
    Random_Time get_delay_time();
    void set_renderer_callback(Create_Renderer callback);
    virtual void execute_event(void * graph_ptr); 
  };
};

#endif // TRAFFIC_SIM_H
